﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ArtificialIntelligence.MovementComponent.AStar
{
    public static class Solver<T> //where T : IEquatable<T>
    {
        public static event EventHandler<SolverResultEventArgs<T>> SolverProgress;

        public static List<Node<T>> Solve(
                                            Node<T> start,
                                            Node<T> goal,
                                            Node<T>[] domain,
                                            Func<Node<T>, int> getHeuristic,
                                            Func<Node<T>, int> getCost,
                                            Func<Node<T>, Node<T>, bool> isEqual
                                        )
        {
            Node<T> currentNode = null;
            bool success = false;
            bool searching = true;
            List<Node<T>> result = new List<Node<T>>();

            start.NodeState = NodeState.Open;	//Add the starting node to the open list

            foreach (Node<T> node in domain)
            {
                node.GetCost = getCost;
                node.GetHeuristic = getHeuristic;
                node.IsEqualFunc = isEqual;
            }

            while (searching)
            {
                currentNode = (
                                    from n in domain
                                    where n.NodeState == NodeState.Open
                                    orderby n.GetTotalCost() ascending
                                    select n
                              ).FirstOrDefault();

                if (null == currentNode)
                {
                    success = false;
                    searching = false;
                }
                else
                {
                    if (currentNode.IsEqual(goal))// found the goal!
                    {
                        success = true;
                        searching = false;
                    }
                    else
                    {
                        currentNode.NodeState = NodeState.Closed;

                        //open the adjacent nodes that have not been examined
                        var neighborsQuery = from n in currentNode.Neighbors
                                             where n.NodeState == NodeState.Undetermined
                                             select n;

                        neighborsQuery.ToList().ForEach(n => { n.NodeState = NodeState.Open; n.PreviousNode = currentNode; });
                    }
                }

                // if no more open nodes, stop - if we have not found 
                // it yet, we can't get to the goal state
                searching &= (
                                from n in domain
                                where n.NodeState == NodeState.Open
                                select n
                             ).Count() > 0;
            }

            result = GetResultList(goal);
            return result;
        }

        private static List<Node<T>> GetResultList(Node<T> topNode)
        {
            Node<T> currentNode = topNode;
            Stack<Node<T>> result = new Stack<Node<T>>();

            // follow the resulting linked list from goal back to start
            do
            {
                result.Push(currentNode);
                currentNode = currentNode.PreviousNode;
            } while (currentNode != null);


            return result.ToList();
        }

    }
}
